import ArrayMethods from "./array";

export function observer(data) {
  // console.log(data);
  // 1 判断data是否被代理过、是否为对象或为空
  if (data.__ob__ || typeof data != 'object' || data == null) {
    return data;
  }
  // 2 对象通过一个类劫持
  return new Observer(data);
}

class Observer {
  constructor(data) {
    // 如果是数组使用defineProperty会浪费很多性能 很少用户会使用数组下标去操作数组
    // vue3中的polyfill  直接就给数组做代理
    // 如果有__ob__属性 说明被观测过了         data.__ob__ = this这样写会死循环
    // 将__ob__变成不可枚举的，walk就不会遍历__ob__了
    Object.defineProperty(data, '__ob__', {
      value: this,
      enumerable: false
    });
    // 判断数据
    if (Array.isArray(data)) { // 数据为数组
      data.__proto__ = ArrayMethods;
      // 如果是数组里面是对象，处理数组对象的劫持 变成响应式的
      this.observeArray(data);
    } else { // 值为对象
      this.walk(data); // 遍历 递归实现所有对象遍历
    }
  }
  walk(data) {
    // 拿到对象中的每个 key
    let keys = Object.keys(data);
    
    // 遍历key
    for (let i = 0, length = keys.length; i < length; i++) {
      // 对我们的每一个属性进行劫持
      let key = keys[i];
      let value = data[key];
      defineReactive(data, key, value);
    }
  }
  observeArray(data) { // [{a:1}]
    for (let i = 0, length = data.length; i < length; i++) {
      // 对数组里的对象进行劫持
      observer(data[i]);
    }
  }
}

// 对对象中的属性进行劫持
function defineReactive(data, key, value) {
  // 属性值为对象时进行递归
  observer(value);
  Object.defineProperty(data, key, {
    get() {
      return value;
    },
    set(newValue) {
      if (newValue == value) return;
      value = newValue;
      // 设置值为对象时进行递归劫持
      observer(value);
    }
  })
}

// vue2 通过 object.defineProperty 进行劫持数据

/*
总结：
（1）对象 {a:1,b:2,c:{d:2}}
    1. Object.defineProperty 缺点 对象中的一个属性
    2. 遍历 {a:1,b:2} 对象第一层进行劫持
    3. 递归 {c:{d:2}} 对属性为对象的进行深度劫持，用户设置对象值时在set中递归
（2）数组 [1,2,'abc',{d:3,e:4,f:{g:6}}]
    1. 函数劫持，劫持数组方法
    2. 创造一个新的数组原型，重写了数组方法
    3. 劫持被调用的数组方法
    4. 数组中的对象会进行响应式处理
 */

/*
* Vue2 中对象的响应式原理
*   给每个属性增加 get 和 set
*   而且是递归操作
*   性能不是很好
*   (用户在写代码时，尽量不要把所有的属性都放在data中，层次尽可能不要太深)
* */